<template>
  <details
    ref="dropdown"
    class="dropdown"
    :class="{
      'dropdown-end': end,
      'dropdown-top': top,
      'dropdown-hover': hover,
    }"
    @blur="closeMenu"
    @focusout="closeMenu"
  >
    <summary
      tabindex="0"
      role="button"
      class="btn gap-0"
      :class="[
        buttonClass,
        {
          'btn-outline': outline,
          'btn-ghost': ghost,
          'btn-link': link,
          'btn-sm': small,
          'btn-lg': large,
        },
      ]"
    >
      <slot name="icon"></slot>
      <slot name="label"></slot>
      <span v-if="!$slots.label">{{ selectedItem && selectedLabel ? selectedLabel : label }}</span>
      <span v-if="showValue && selectedItem" class="opacity-60">
        ：{{ getItemLabel(selectedItem) }}
      </span>
    </summary>

    <ul
      tabindex="0"
      class="dropdown-content menu z-10 bg-base-100 rounded-lg shadow-lg border border-base-content/10"
      :class="[menuClass, { 'p-2': padding }]"
      :style="{ width: width }"
      @focus="listHasFocus = true"
      @focusout="listHasFocus = false"
      @blur="listHasFocus = false"
    >
      <slot name="header"></slot>
      <li
        v-for="(item, index) in options"
        :key="getItemKey(item, index)"
        @click="selectItem(item, index)"
        class="rounded-lg"
        :class="{
          'bg-base-200': isSelected(item),
          'disabled': isItemDisabled(item),
        }"
      >
        <a :class="{ 'text-base-content/50': isItemDisabled(item) }">
          <slot name="item" :item="item" :index="index">
            {{ getItemLabel(item) }}
          </slot>
        </a>
      </li>
      <slot name="footer"></slot>
    </ul>
  </details>
</template>

<script setup lang="ts">
import { onMounted, ref } from "vue";

type OptionItem<T> = T extends Record<string, any> ? T : string;

interface Props {
  options: any[];
  label?: string;
  selectedLabel?: string;
  labelField?: string;
  valueField?: string;
  keyField?: string;
  width?: string;
  buttonClass?: string;
  menuClass?: string;
  showValue?: boolean;
  padding?: boolean;
  outline?: boolean;
  ghost?: boolean;
  link?: boolean;
  small?: boolean;
  large?: boolean;
  end?: boolean;
  top?: boolean;
  hover?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  label: "",
  selectedLabel: "",
  labelField: "label",
  valueField: "value",
  keyField: "id",
  width: "13rem",
  buttonClass: "",
  menuClass: "",
  showValue: false,
  padding: true,
  outline: false,
  ghost: false,
  link: false,
  small: false,
  large: false,
  end: false,
  top: false,
  hover: false,
});

export type DaisyDropdownSelectArgument<T> = {
  item: OptionItem<T>;
  index: number;
};
const emit = defineEmits<{
  (e: "change", item: DaisyDropdownSelectArgument<any>): void;
  (e: "select", item: DaisyDropdownSelectArgument<any>): void;
}>();

const dropdown = ref<HTMLElement | null>(null);
const listHasFocus = ref(false);
const selectedItem = ref<OptionItem<any>>(props.options[0]);

const isObject = (item: any): item is OptionItem<Record<string, any>> => {
  return typeof item === "object" && item !== null;
};


const getItemKey = (item: OptionItem<any>, index: number): string | number => {
  if (isObject(item)) {
    return item[props.keyField] || index;
  }
  return index;
};
const getItemLabel = (item: OptionItem<any>): string => {
  if (isObject(item)) {
    return item[props.labelField] ?? "";
  }
  return String(item);
};
const getItemValue = (item: OptionItem<any>): any => {
  if (isObject(item)) {
    return item[props.valueField];
  }
  return item;
};


const isItemDisabled = (item: OptionItem<any>): boolean => {
  if (isObject(item)) {
    return Boolean(item.disabled);
  }
  return false;
};
const isSelected = (item: OptionItem<any>): boolean => {
  if (!selectedItem.value) return false;
  return getItemValue(item) === getItemValue(selectedItem);
};

const closeMenu = () => {
  setTimeout(() => {
    if (listHasFocus.value) return;
    dropdown.value?.removeAttribute("open");
  }, 100);
};

const selectItem = (item: OptionItem<any>, index: number) => {
  if (isItemDisabled(item)) return;

  selectedItem.value = item;
  emit("change", { item, index });
  emit("select", { item, index });

  listHasFocus.value = false;
  closeMenu();
};
</script>

<style scoped>
.disabled {
  cursor: not-allowed;
  pointer-events: none;
}
</style>
